各位戰士,歡迎來到第十八天的戰場。在 UI 流暢度的陣地戰中,最常見的敵人之一就是「過於複雜的佈局」。一個層層堆疊、深度過深的佈局,就像一個臃腫的官僚體系,會讓 Android 系統在渲染的「測量 (Measure)」與「佈局 (Layout)」階段執行大量重複且低效的工作。
在 RecyclerView
這樣的場景中,這種低效會被成百上千倍地放大,每一次滾動都可能引發卡頓。今天的任務,就是學習如何精簡我們的佈局結構,打造一個扁平化、高效率的指揮體系。
在過去,LinearLayout
和 RelativeLayout
是我們佈局的主力。但為了實現稍複雜的 UI,我們常常會陷入「佈局巢狀 (Nesting)」的陷阱。
想像一個常見的卡片佈局:
<LinearLayout
android:orientation="vertical" ...>
<TextView ... />
<LinearLayout
android:orientation="horizontal" ...>
<ImageView android:id="@+id/avatar" ... />
<TextView android:id="@+id/name" ... />
</LinearLayout
</LinearLayout>
這樣的結構會產生一個深度為 3 的視圖層級。更糟糕的是,如果你在 LinearLayout
中使用了 layout_weight
屬性,系統為了計算剩餘空間該如何分配,往往需要對其子 View 進行兩次測量。這就是臭名昭著的「雙重稅負 (Double Taxation)」問題,它會讓測量階段的耗時加倍。
主力武器:ConstraintLayout
的降維打擊
為了解決巢狀佈局的頑疾,Google 推出了 ConstraintLayout
(約束佈局)。它的核心思想截然不同:
ConstraintLayout
讓你透過建立「約束關係」,來描述佈局中各個元件 (View) 之間的位置關係,從而實現幾乎完全扁平化的佈局層級。
讓我們用 ConstraintLayout
來重構上面的例子:ConstraintLayout
讓你透過建立「約束關係」,來描述佈局中各個元件 (View) 之間的位置關係,從而實現幾乎完全扁平化的佈局層級。
讓我們用 ConstraintLayout
來重構上面的例子:
<androidx.constraintlayout.widget.ConstraintLayout ...>
<TextView
android:id="@+id/title"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" ... />
<ImageView
android:id="@+id/avatar"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="parent" ... />
<TextView
android:id="@+id/name"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toTopOf="@id/avatar"
app:layout_constraintBottom_toBottomOf="@id/avatar" ... />
</androidx.constraintlayout.widget.ConstraintLayout>
層級扁平化:我們用一層 ConstraintLayout
就完成了之前三層 LinearLayout
才能做到的事。視圖樹的深度從 3 降到了 2。
性能提升:測量和佈局的計算量大幅減少,沒有了「雙重稅負」的風險。
靈活性:ConstraintLayout
還提供了 Guideline
, Barrier
, Chain
等強大功能,可以實現遠比傳統佈局更複雜的 UI,同時保持扁平的層級。
在所有可以拍扁層級的場景,ConstraintLayout
都應該是你的首選武器。
特種部隊:<merge>
標籤的奇襲
有時候,我們為了重用性,會把一部分 UI 抽成一個獨立的 XML 檔案,然後用 <include>
標籤引入。這時,一個新的敵人——「冗餘的父佈局」——就可能出現。
場景:假設你有一個主佈局是 LinearLayout
,而你抽出來的 custom_title.xml
的根佈局也是一個 LinearLayout
。
<LinearLayout android:orientation="vertical" ...>
<include layout="@layout/custom_title" />
</LinearLayout>
<LinearLayout android:orientation="horizontal" ...>
<ImageView ... />
<TextView ... />
</LinearLayout>
當 include
發生時,視圖結構會變成 LinearLayout
> LinearLayout
> (ImageView, TextView)
,產生了一個完全多餘的佈局層級。
解決方案:使用 <merge>
標籤。
<merge>
是一個神奇的標籤,它本身不會被繪製到視圖層級中,而是會將它的子 View 直接合併到它被 include
進去的父佈局裡。
<merge xmlns:android="[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)">
<ImageView ... />
<TextView ... />
</merge>
使用 <merge>
後,視圖結構變成了 LinearLayout
> (ImageView, TextView)
,我們兵不血刃地消滅了一個冗餘的佈局層級。
我們如何確認佈局真的被優化了?
Android Studio 內建的 Layout Inspector (工具列 Tools > Layout Inspector) 是我們的驗收工具。它可以視覺化地展示 App 當前畫面的視圖樹結構。你可以對比優化前後的視圖樹,清晰地看到層級是否減少。
當然,最終的勝利證明,還是在 Perfetto 的 Trace 報告中看到那個曾經耗時的 onMeasure
/onLayout
區塊,變得又短又快。
今天,我們學習了佈局優化戰役中的核心戰術:盡一切可能壓平視圖層級。
layout_weight
的 LinearLayout
,是性能的殺手。ConstraintLayout
,用它來構建複雜但扁平的 UI。<merge>
標籤,在 <include>
場景中消除冗餘的父佈局。佈局的戰場已經清理乾淨。但 UI 流暢度的戰爭遠未結束。明天,我們將把目光投向另一個、也是最常見的 Jank 發生地——RecyclerView。我們將學習一些超越 ViewHolder 模式的進階優化技巧。
我們明天見!